package basics.io.chatroom;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Logger;

/**
 * @author or2
 * @Description
 * @create 2021年09月17日 时间: 20:12
 */
public class ChatClient {

    public static final String DEFAULT_SERVER_HOST = "127.0.0.1";
    public static final int DEFAULT_SERVER_PORT = 8888;
    public static final String EXIT = "exit";
    private static final int BYTE_CAPACITY = 1024;

    private final String host;
    private final int port;
    /**
     * 核心 - 通道选择器
     */
    private Selector selector;
    /**
     * 字节缓冲区
     */
    private final ByteBuffer rBuffer = ByteBuffer.allocate(BYTE_CAPACITY);
    private final ByteBuffer wBuffer = ByteBuffer.allocate(BYTE_CAPACITY);
    /**
     * 统一编码
     */
    private final Charset charset = StandardCharsets.UTF_8;
    private final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    private SocketChannel client;

    public ChatClient() {
        this(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
    }

    public ChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() {
        try (SocketChannel clientResource = SocketChannel.open()) {
            client = clientResource;
            client.configureBlocking(false);

            selector = Selector.open();
            client.register(selector, SelectionKey.OP_CONNECT);
            client.connect(new InetSocketAddress(host, port));
            Logger.getLogger("client").info("客户端[" + client.socket().getPort() + "]开始连接...");

            while (true) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    handle(selectionKey);
                }
                selectionKeys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handle(SelectionKey selectionKey) throws IOException {
        SocketChannel client = (SocketChannel) selectionKey.channel();
//        连接就绪事件
        if (selectionKey.isConnectable()) {
            if (client.isConnectionPending()) {
                client.finishConnect();
                threadPoolExecutor.submit(new UserInputHandler(this));
            }
            client.register(selector, SelectionKey.OP_READ);
        }
//        服务器转发事件
        else if (selectionKey.isReadable()) {
            String msg = receive(client);
            if (msg.isEmpty()) {
                selector.close();
            } else {
                System.out.println(msg);
            }
        }
    }

    public void send(String msg) throws IOException {
        if (msg.isEmpty()) {
            return;
        }

        wBuffer.clear();
        wBuffer.put(charset.encode(msg));
        wBuffer.flip();
        while (wBuffer.hasRemaining()) {
            client.write(wBuffer);
        }

        if (exit(msg)) {
            selector.close();
        }
    }

    /**
     * 从通道中读取数据
     *
     * @param client 客户端通道
     * @return 读取到的字符串
     * @throws IOException
     */
    private String receive(SocketChannel client) throws IOException {
        rBuffer.clear();
        while (client.read(rBuffer) > 0) ;
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }

    /**
     * 客户端退出
     *
     * @param msg 根据消息内容判断
     * @return 是否退出?
     */
    private boolean exit(String msg) {
        return msg.equals(EXIT);
    }
}
